import { Matrix4 } from "../../utils/Matrix4";
import { MatrixUtil } from "../../utils/MatrixUtil";
import { Primitives } from "../../utils/Primitives";
import { TextrueUtil } from "../../utils/TextureUtil";
import { Util } from "../../utils/Util";
import { WebGLUtil } from "../../utils/WebGLUtil";
import { IProgramInfo } from "../interface/IDefine";
import { Render } from "../Render";
import { WebglTemplateBase } from "./WebglTemplateBase";

/*
 * @Author: Snow
 * @Date: 2021-10-11 15:08:49
 * @Description:  文字图集
 */
export class TemplateText3DAtlas extends WebglTemplateBase {

    private textProgramInfo: IProgramInfo;

    protected override initUITools(): void {
        super.initUITools();
    }

    protected override initProgram(): void {
        this.vsSource = `
            attribute vec4 a_position;
            attribute vec4 a_color;
            
            uniform mat4 u_matrix;
            
            varying vec4 v_color;
            
            void main() {
                // Multiply the position by the matrix.
                gl_Position = u_matrix * a_position;
                
                // Pass the color to the fragment shader.
                v_color = a_color;
            }
        `;

        this.fsSource = `
            precision mediump float;

            // Passed in from the vertex shader.
            varying vec4 v_color;
            
            void main() {
                gl_FragColor = v_color;
            }
        `;

        let textVsSource = `
            attribute vec4 a_position;
            
            attribute vec2 a_texcoord;

            uniform mat4 u_matrix;

            varying vec2 v_texcoord;
            
            
            void main() {
                // Multiply the position by the matrix.
                gl_Position = u_matrix * a_position;

                v_texcoord = a_texcoord;
            }
        `;

        let textFsSource = `
            precision mediump float;

            // Passed in from the vertex shader.
            uniform sampler2D u_texture;
            
            varying vec2 v_texcoord;

            void main() {
                gl_FragColor = texture2D(u_texture, v_texcoord);
                //gl_FragColor = vec4(1,0,0,1);
            }
        `;
        super.initProgram();

        this.textProgramInfo = WebGLUtil.createProgramInfo(Render.GL, textVsSource, textFsSource);
    }

    protected override initBuffers(): void {
        super.initBuffers();
        const gl = Render.GL;
        // an indexed quad

        var fontInfo = {
            letterHeight: 8,
            spaceWidth: 8,
            spacing: -1,
            textureWidth: 64,
            textureHeight: 40,
            glyphInfos: {
                'a': { x: 0, y: 0, width: 8, },
                'b': { x: 8, y: 0, width: 8, },
                'c': { x: 16, y: 0, width: 8, },
                'd': { x: 24, y: 0, width: 8, },
                'e': { x: 32, y: 0, width: 8, },
                'f': { x: 40, y: 0, width: 8, },
                'g': { x: 48, y: 0, width: 8, },
                'h': { x: 56, y: 0, width: 8, },
                'i': { x: 0, y: 8, width: 8, },
                'j': { x: 8, y: 8, width: 8, },
                'k': { x: 16, y: 8, width: 8, },
                'l': { x: 24, y: 8, width: 8, },
                'm': { x: 32, y: 8, width: 8, },
                'n': { x: 40, y: 8, width: 8, },
                'o': { x: 48, y: 8, width: 8, },
                'p': { x: 56, y: 8, width: 8, },
                'q': { x: 0, y: 16, width: 8, },
                'r': { x: 8, y: 16, width: 8, },
                's': { x: 16, y: 16, width: 8, },
                't': { x: 24, y: 16, width: 8, },
                'u': { x: 32, y: 16, width: 8, },
                'v': { x: 40, y: 16, width: 8, },
                'w': { x: 48, y: 16, width: 8, },
                'x': { x: 56, y: 16, width: 8, },
                'y': { x: 0, y: 24, width: 8, },
                'z': { x: 8, y: 24, width: 8, },
                '0': { x: 16, y: 24, width: 8, },
                '1': { x: 24, y: 24, width: 8, },
                '2': { x: 32, y: 24, width: 8, },
                '3': { x: 40, y: 24, width: 8, },
                '4': { x: 48, y: 24, width: 8, },
                '5': { x: 56, y: 24, width: 8, },
                '6': { x: 0, y: 32, width: 8, },
                '7': { x: 8, y: 32, width: 8, },
                '8': { x: 16, y: 32, width: 8, },
                '9': { x: 24, y: 32, width: 8, },
                '-': { x: 32, y: 32, width: 8, },
                '*': { x: 40, y: 32, width: 8, },
                '!': { x: 48, y: 32, width: 8, },
                '?': { x: 56, y: 32, width: 8, },
            },
        };


        let fBufferInfo = Primitives.createBufferInfoFunc(Primitives.create3DFVertices)(gl);

        var textBufferInfo = {
            attribs: {
                a_position: { buffer: gl.createBuffer(), numComponents: 2, },
                a_texcoord: { buffer: gl.createBuffer(), numComponents: 2, },
            },
            numElements: 0,
        };


        // Create a texture.
        var glyphTex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, glyphTex);
        // Fill the texture with a 1x1 blue pixel.
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
            new Uint8Array([0, 0, 255, 255]));
        // Asynchronously load an image
        var image = new Image();
        image.src = "8x8-font.png";
        image.addEventListener('load', function () {
            // Now that the image has loaded make copy it to the texture.
            gl.bindTexture(gl.TEXTURE_2D, glyphTex);
            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        });

        var names = [
            "anna",   // 0
            "colin",  // 1
            "james",  // 2
            "danny",  // 3
            "kalin",  // 4
            "hiro",   // 5
            "eddie",  // 6
            "shu",    // 7
            "brian",  // 8
            "tami",   // 9
            "rick",   // 10
            "gene",   // 11
            "natalie",// 12,
            "evan",   // 13,
            "sakura", // 14,
            "kai",    // 15,
        ];

        var fUniforms = {
            u_matrix: Matrix4.identity(),
        };

        var textUniforms = {
            u_matrix: Matrix4.identity(),
            u_texture: glyphTex,
            u_color: [0, 0, 0, 1],  // black
        };


        var translation = [0, 30, 0];
        var rotation = [Util.degToRad(190), Util.degToRad(0), Util.degToRad(0)];
        var scale = [1, 1, 1];
        var fieldOfViewRadians = Util.degToRad(60);
        var rotationSpeed = 1.2;

        var then = 0;


        let drawScene = () => {
            let time = this.ts;
            time = time;
            var deltaTime = time - then;
            // Remember the current time for the next frame.
            then = time;
            let now = time;
            WebGLUtil.resizeCanvasToMatchDisplaySize(gl);

            // Every frame increase the rotation a little.
            rotation[1] += rotationSpeed * deltaTime;

            gl.enable(gl.DEPTH_TEST);
            gl.enable(gl.CULL_FACE);
            gl.disable(gl.BLEND);
            gl.depthMask(true);

            // Clear the canvas AND the depth buffer.
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);



            // Compute the projection matrix
            let aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
            var projectionMatrix = Matrix4.perspective(fieldOfViewRadians, aspect, 1, 2000);

            // Compute the camera's matrix using look at.
            let cameraRadius = 360;
            let cameraPosition = [Math.cos(now) * cameraRadius, 0, Math.sin(now) * cameraRadius];
            let target = [0, 0, 0];
            let up = [0, 1, 0];
            let cameraMatrix = Matrix4.lookAt(cameraPosition, target, up);

            // Make a view matrix from the camera matrix.
            let viewMatrix = Matrix4.invert(cameraMatrix);

            var textPositions = [];

            // setup to draw the 'F'
            gl.useProgram(this.programInfo.program);

            WebGLUtil.setBuffersAndAttributes(gl, this.programInfo, fBufferInfo);

            // draw the Fs.
            var spread = 170;
            for (var yy = -1; yy <= 1; ++yy) {
                for (var xx = -2; xx <= 2; ++xx) {
                    var fWorldMatrix = Matrix4.translation(
                        translation[0] + xx * spread, translation[1] + yy * spread, translation[2]);
                    fWorldMatrix = Matrix4.multiply(fWorldMatrix, Matrix4.rotateX(rotation[0]));
                    fWorldMatrix = Matrix4.multiply(fWorldMatrix, Matrix4.rotateY(rotation[1] + yy * xx * 0.2));
                    fWorldMatrix = Matrix4.multiply(fWorldMatrix, Matrix4.rotateZ(rotation[2] + now + (yy * 3 + xx) * 0.1));
                    fWorldMatrix = Matrix4.multiply(fWorldMatrix, Matrix4.scaling(scale[0], scale[1], scale[2]));
                    fWorldMatrix = Matrix4.multiply(fWorldMatrix, Matrix4.translation(-50, -75, 0));
                    
                    let tempMatrix2 = Matrix4.multiply(viewMatrix, fWorldMatrix);
                    textPositions.push([tempMatrix2[12], tempMatrix2[13], tempMatrix2[14]]);
                    fUniforms.u_matrix = Matrix4.multiply(projectionMatrix, tempMatrix2);

                    WebGLUtil.setUniforms(this.programInfo, fUniforms);

                    // Draw the geometry.
                    gl.drawElements(gl.TRIANGLES, fBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
                }
            }

            gl.enable(gl.BLEND);
            gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
            gl.depthMask(false);

            // draw the text

            // setup to draw the text.
            // Because every letter uses the same attributes and the same progarm
            // we only need to do this once.
            gl.useProgram(this.textProgramInfo.program);
            WebGLUtil.setBuffersAndAttributes(gl, this.textProgramInfo, textBufferInfo);

            textPositions.forEach((pos, ndx) => {

                var name = names[ndx];
                var s = name + ":" + pos[0].toFixed(0) + "," + pos[1].toFixed(0) + "," + pos[2].toFixed(0);
                var vertices = this.makeVerticesForString(fontInfo, s);

                // update the buffers
                textBufferInfo.attribs.a_position.numComponents = 2;
                gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_position.buffer);
                gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.position, gl.DYNAMIC_DRAW);
                gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_texcoord.buffer);
                gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.texcoord, gl.DYNAMIC_DRAW);

                // use just the position of the 'F' for the text

                // because pos is in view space that means it's a vector from the eye to
                // some position. So translate along that vector back toward the eye some distance
                var fromEye = Matrix4.normalize(pos);
                var amountToMoveTowardEye = 150;  // because the F is 150 units long
                var viewX = pos[0] - fromEye[0] * amountToMoveTowardEye;
                var viewY = pos[1] - fromEye[1] * amountToMoveTowardEye;
                var viewZ = pos[2] - fromEye[2] * amountToMoveTowardEye;
                var desiredTextScale = -1 / gl.canvas.height * 2;  // 1x1 pixels
                var scale = viewZ * desiredTextScale;

                var textMatrix = Matrix4.multiply(projectionMatrix, Matrix4.translation(viewX, viewY, viewZ));
                // scale the F to the size we need it. 去掉透视效果
                textMatrix = Matrix4.multiply(textMatrix, Matrix4.scaling(scale, scale, 1));

                // set texture uniform
                Matrix4.copy(textMatrix, textUniforms.u_matrix);
                WebGLUtil.setUniforms(this.textProgramInfo, textUniforms);

                // Draw the text.
                gl.drawArrays(gl.TRIANGLES, 0, vertices.numVertices);
            });

        }
        this.draw = drawScene;
    }

    private makeVerticesForString(fontInfo, s) {
        var len = s.length;
        var numVertices = len * 6;
        var positions = new Float32Array(numVertices * 2);
        var texcoords = new Float32Array(numVertices * 2);
        var offset = 0;
        var x = 0;
        var maxX = fontInfo.textureWidth;
        var maxY = fontInfo.textureHeight;
        for (var ii = 0; ii < len; ++ii) {
            var letter = s[ii];
            var glyphInfo = fontInfo.glyphInfos[letter];
            if (glyphInfo) {
                var x2 = x + glyphInfo.width;
                var u1 = glyphInfo.x / maxX;
                var v1 = (glyphInfo.y + fontInfo.letterHeight - 1) / maxY;
                var u2 = (glyphInfo.x + glyphInfo.width - 1) / maxX;
                var v2 = glyphInfo.y / maxY;

                // 6 vertices per letter
                positions[offset + 0] = x;
                positions[offset + 1] = 0;
                texcoords[offset + 0] = u1;
                texcoords[offset + 1] = v1;

                positions[offset + 2] = x2;
                positions[offset + 3] = 0;
                texcoords[offset + 2] = u2;
                texcoords[offset + 3] = v1;

                positions[offset + 4] = x;
                positions[offset + 5] = fontInfo.letterHeight;
                texcoords[offset + 4] = u1;
                texcoords[offset + 5] = v2;

                positions[offset + 6] = x;
                positions[offset + 7] = fontInfo.letterHeight;
                texcoords[offset + 6] = u1;
                texcoords[offset + 7] = v2;

                positions[offset + 8] = x2;
                positions[offset + 9] = 0;
                texcoords[offset + 8] = u2;
                texcoords[offset + 9] = v1;

                positions[offset + 10] = x2;
                positions[offset + 11] = fontInfo.letterHeight;
                texcoords[offset + 10] = u2;
                texcoords[offset + 11] = v2;

                x += glyphInfo.width + fontInfo.spacing;
                offset += 12;
            } else {
                // we don't have this character so just advance
                x += fontInfo.spaceWidth;
            }
        }

        // return ArrayBufferViews for the portion of the TypedArrays
        // that were actually used.
        return {
            arrays: {
                position: new Float32Array(positions.buffer, 0, offset),
                texcoord: new Float32Array(texcoords.buffer, 0, offset),
            },
            numVertices: offset / 2,
        };
    }
}